/*-
 * Copyright (c) 2014 Shang-Rong Cai. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $ DIDF: cvtflt.c    Rev. 0.1.1    2014-04-17T00:17+08:00 $
 */

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

#include <did.h>


#define FLAG_VERBOSE_FALSE 0
#define FLAG_VERBOSE_TRUE  1


void usage();

int check_index_type(const unsigned int typ);

int check_entry_type(const unsigned int typ);


/* Context For Creating DID */
typedef struct
{
    uint64_t fst_sz;  /* Least upper bound of first index */
    uint64_t snd_sz;  /* Least upper bound of second index */
    uint64_t ent_sz;  /* Number of data Entries */

    void *fst_idx_lst;  /* First index list */
    void *snd_idx_lst;  /* Second index list */
    void *dat_ent_lst;  /* Data entry list */
} didx_t;

int didx_init(size_t fz, size_t sz, size_t ez, unsigned int ityp, unsigned int ftyp, didx_t *didx);

int didx_dest(didx_t *didx);

void didx_stdout(didx_t *didx, unsigned int ityp, unsigned int ftyp);


int text_format_read_head(FILE *fpif, size_t *fz, size_t *sz, size_t *ez);

int text_format_read_data(FILE *fpif, unsigned int ityp, unsigned int ftyp, void *fi_lst, void *si_lst, void *de_lst);


int main(int argc, char *argv[])
{
    int ierr = ERRNO_UNDEFINED;

    int cur_argc;
    char **cur_argv;

    int opti;

    unsigned int flg_ityp;  /* Flag of arugment for index type */
    unsigned int flg_ftyp;  /* Flag of arugment for data entry type of float type */

    int flg_verbose;  /* Show verbose information */

    size_t fst_idxz;  /* Least upper bound of first index */
    size_t snd_idxz;  /* Least upper bound of second index */
    size_t dat_entz;  /* Number of indexed data entries */

    didx_t didx;

    did_head_t hdprt;
    did_meta_t mtprt;
    did_body_t bdprt;

    FILE *fpif;  /* Input text file */
    FILE *fpof;  /* Output DID file */

    /* Initialize */
    cur_argc = argc;
    cur_argv = argv;

    flg_ityp = DID_HEAD_INDEX_T_I32;
    flg_ftyp = DID_HEAD_DATA_T_F64;

    flg_verbose = FLAG_VERBOSE_FALSE;

    while ((opti = getopt(cur_argc, cur_argv, "f:i:v")) != -1)
    {
        switch (opti)
        {
            case 'f':
                sscanf(optarg, "%u", &flg_ftyp);
                break;
            case 'i':
                sscanf(optarg, "%u", &flg_ityp);
                break;
            case 'v':
                flg_verbose = FLAG_VERBOSE_TRUE;
                break;
            default:
                usage();
                break;
        }
    }

    cur_argc -= optind;
    cur_argv += optind;

    if (cur_argc < 2)
    {
        usage();
        goto EXIT_MAIN;
    }

    /* Check Type */
    ierr = check_index_type(flg_ityp);
    if (ierr != ERRNO_UNDEFINED)
    {
        goto EXIT_MAIN;
    }

    ierr = check_entry_type(flg_ftyp);
    if (ierr != ERRNO_UNDEFINED)
    {
        goto EXIT_MAIN;
    }

    /* Read Input File */
    fpif = fopen(cur_argv[0], "r");
    if (fpif == NULL)
    {
        perror(cur_argv[0]);
        goto EXIT_MAIN;
    }

    ierr = text_format_read_head(fpif, &fst_idxz, &snd_idxz, &dat_entz);

    if (flg_verbose == FLAG_VERBOSE_TRUE)
    {
        printf("INPUT FILE: %s\n", cur_argv[0]);
        printf("Least upper bound of first index: %zd\n", fst_idxz);
        printf("Least upper bound of second index: %zd\n", snd_idxz);
        printf("Data Entries: %zd\n", dat_entz);
    }

    ierr = didx_init(fst_idxz, snd_idxz, dat_entz, flg_ityp, flg_ftyp, &didx);

    ierr = text_format_read_data(fpif, flg_ityp, flg_ftyp, didx.fst_idx_lst, didx.snd_idx_lst, didx.dat_ent_lst);

    fclose(fpif);  /* Close input file */

    if (flg_verbose == FLAG_VERBOSE_TRUE)
    {
        didx_stdout(&didx, flg_ityp, flg_ftyp);
    }

    /* Construct Output File */
    ierr = did_head_set_info(&hdprt, (uint8_t) flg_ityp, (uint8_t) flg_ftyp);
    ierr = did_meta_set_info(&mtprt, &hdprt, (uint64_t) fst_idxz, (uint64_t) snd_idxz, (uint64_t) dat_entz);

    if (flg_verbose == FLAG_VERBOSE_TRUE)
    {
        printf("OUTPUT FILE: %s\n", cur_argv[1]);
        ierr = did_head_stdout(&hdprt);
        ierr = did_meta_stdout(&mtprt);
    }

    did_body_create(&bdprt, &hdprt, &mtprt, (void*) didx.fst_idx_lst, (void*) didx.snd_idx_lst, (void*) didx.dat_ent_lst);

    ierr = didx_dest(&didx);

    fpof = fopen(cur_argv[1], "w");
    if (fpof == NULL)
    {
        perror(cur_argv[1]);
        goto EXIT_MAIN;
    }

    /* Write Output File */
    ierr = did_write(fpof, &hdprt, &mtprt, &bdprt);

    fclose(fpof);

    did_body_delete(&bdprt);

    EXIT_MAIN:

    return ierr;
}


void usage()
{
    printf("usage: txt2fdid [-f type] [-i type] input-file output-file\n");
}


int check_index_type(const unsigned int typ)
{
    int ierr = ERRNO_UNDEFINED;

    switch (typ)
    {
        case DID_HEAD_INDEX_T_I32:
            break;
        case DID_HEAD_INDEX_T_I64:
            break;
        default:
            printf("Not supported parameter\n");
            ierr = ERRNO_UNKNOWN_TYPE;
            break;
    }

    return ierr;
}


int check_entry_type(const unsigned int typ)
{
    int ierr = ERRNO_UNDEFINED;

    switch (typ)
    {
        case DID_HEAD_DATA_T_F32:
            break;
        case DID_HEAD_DATA_T_F64:
            break;
        default:
            printf("Not supported parameter\n");
            ierr = ERRNO_UNKNOWN_TYPE;
            break;
    }

    return ierr;
}


int didx_init(size_t fz, size_t sz, size_t ez, unsigned int ityp, unsigned int ftyp, didx_t *didx)
{
    int ierr = ERRNO_UNDEFINED;

    /* Set Size */
    didx->fst_sz = (uint64_t) fz;
    didx->snd_sz = (uint64_t) sz;
    didx->ent_sz = (uint64_t) ez;

    /* Allocate Memory */
    switch (ityp)
    {
        case DID_HEAD_INDEX_T_I32:
            didx->fst_idx_lst = calloc(didx->ent_sz, sizeof(int32_t));
            didx->snd_idx_lst = calloc(didx->ent_sz, sizeof(int32_t));
            break;
        case DID_HEAD_INDEX_T_I64:
            didx->fst_idx_lst = calloc(didx->ent_sz, sizeof(int64_t));
            didx->snd_idx_lst = calloc(didx->ent_sz, sizeof(int64_t));
            break;
        default:
            didx->fst_idx_lst = NULL;
            didx->snd_idx_lst = NULL;
            break;
    }

    switch (ftyp)
    {
        case DID_HEAD_DATA_T_F32:
            didx->dat_ent_lst = calloc(didx->ent_sz, sizeof(float));
            break;
        case DID_HEAD_DATA_T_F64:
            didx->dat_ent_lst = calloc(didx->ent_sz, sizeof(double));
            break;
        default:
            didx->dat_ent_lst = NULL;
            break;
    }

    return ierr;
}


int didx_dest(didx_t *didx)
{
    int ierr = ERRNO_UNDEFINED;

    free(didx->fst_idx_lst);
    free(didx->snd_idx_lst);
    free(didx->dat_ent_lst);

    return ierr;
}


void didx_stdout(didx_t *didx, unsigned int ityp, unsigned int ftyp)
{
    uint64_t ui;

    int32_t *ptr_i32_f, *ptr_i32_s;
    int64_t *ptr_i64_f, *ptr_i64_s;

    float *ptr_f32;
    double *ptr_f64;

    ptr_i32_f = (int32_t*) didx->fst_idx_lst;
    ptr_i64_f = (int64_t*) didx->fst_idx_lst;
    ptr_i32_s = (int32_t*) didx->snd_idx_lst;
    ptr_i64_s = (int64_t*) didx->snd_idx_lst;

    ptr_f32 = (float*) didx->dat_ent_lst;
    ptr_f64 = (double*) didx->dat_ent_lst;

    switch (ityp)
    {
        case DID_HEAD_INDEX_T_I32:
            switch (ftyp)
            {
                case DID_HEAD_DATA_T_F32:
                    for (ui = 0; ui < didx->ent_sz; ui++)
                    {
                        printf("%7lu: (%7d, %7d)  % .7E\n", ui, ptr_i32_f[ui], ptr_i32_s[ui], ptr_f32[ui]);
                    }
                    break;
                case DID_HEAD_DATA_T_F64:
                    for (ui = 0; ui < didx->ent_sz; ui++)
                    {
                        printf("%7lu: (%7d, %7d)  % .15lE\n", ui, ptr_i32_f[ui], ptr_i32_s[ui], ptr_f64[ui]);
                    }
                    break;
            }
            break;
        case DID_HEAD_INDEX_T_I64:
            switch (ftyp)
            {
                case DID_HEAD_DATA_T_F32:
                    for (ui = 0; ui < didx->ent_sz; ui++)
                    {
                        printf("%7lu: (%15ld, %15ld)  % .7E\n", ui, ptr_i64_f[ui], ptr_i64_s[ui], ptr_f32[ui]);
                    }
                    break;
                case DID_HEAD_DATA_T_F64:
                    for (ui = 0; ui < didx->ent_sz; ui++)
                    {
                        printf("%7lu: (%15ld, %15ld)  % .15lE\n", ui, ptr_i64_f[ui], ptr_i64_s[ui], ptr_f64[ui]);
                    }
                    break;
            }
            break;
    }
}


int text_format_read_head(FILE *fpif, size_t *fz, size_t *sz, size_t *ez)
{
    int ierr = ERRNO_UNDEFINED;

    fseek(fpif, 0, SEEK_SET);

    fscanf(fpif, "%zd %zd %zd", fz, sz, ez);

    return ierr;
}


int text_format_read_data(FILE *fpif, unsigned int ityp, unsigned int ftyp, void *fi_lst, void *si_lst, void *de_lst)
{
    int ierr = ERRNO_UNDEFINED;

    size_t zi;
    size_t fsz, ssz, esz;

    int32_t buf_i32_f, buf_i32_s;
    int64_t buf_i64_f, buf_i64_s;

    float buf_f32;
    double buf_f64;

    int32_t *ptr_i32_f, *ptr_i32_s;
    int64_t *ptr_i64_f, *ptr_i64_s;

    float *ptr_f32;
    double *ptr_f64;

    /* Set Increased Pointer */
    ptr_i32_f = (int32_t*) fi_lst;
    ptr_i64_f = (int64_t*) fi_lst;

    ptr_i32_s = (int32_t*) si_lst;
    ptr_i64_s = (int64_t*) si_lst;

    ptr_f32 = (float*) de_lst;
    ptr_f64 = (double*) de_lst;

    /* Read Head */
    fseek(fpif, 0, SEEK_SET);
    fscanf(fpif, "%zd %zd %zd", &fsz, &ssz, &esz);

    /* Read Data */
    switch (ityp)
    {
        case DID_HEAD_INDEX_T_I32:
            switch (ftyp)
            {
                case DID_HEAD_DATA_T_F32:
                    for (zi = 0; zi < esz; zi++)
                    {
                        fscanf(fpif, "%d %d %f", &buf_i32_f, &buf_i32_s, &buf_f32);
                        ptr_i32_f[zi] = buf_i32_f;
                        ptr_i32_s[zi] = buf_i32_s;
                        ptr_f32[zi] = buf_f32;
                    }
                    break;
                case DID_HEAD_DATA_T_F64:
                    for (zi = 0; zi < esz; zi++)
                    {
                        fscanf(fpif, "%d %d %lf", &buf_i32_f, &buf_i32_s, &buf_f64);
                        ptr_i32_f[zi] = buf_i32_f;
                        ptr_i32_s[zi] = buf_i32_s;
                        ptr_f64[zi] = buf_f64;
                    }
                    break;
                default:
                    ierr = ERRNO_UNKNOWN_TYPE;
                    break;
            }
            break;
        case DID_HEAD_INDEX_T_I64:
            switch (ftyp)
            {
                case DID_HEAD_DATA_T_F32:
                    for (zi = 0; zi < esz; zi++)
                    {
                        fscanf(fpif, "%ld %ld %f", &buf_i64_f, &buf_i64_s, &buf_f32);
                        ptr_i64_f[zi] = buf_i64_f;
                        ptr_i64_s[zi] = buf_i64_s;
                        ptr_f32[zi] = buf_f32;
                    }
                    break;
                case DID_HEAD_DATA_T_F64:
                    for (zi = 0; zi < esz; zi++)
                    {
                        fscanf(fpif, "%ld %ld %lf", &buf_i64_f, &buf_i64_s, &buf_f64);
                        ptr_i64_f[zi] = buf_i64_f;
                        ptr_i64_s[zi] = buf_i64_s;
                        ptr_f64[zi] = buf_f64;
                    }
                    break;
                default:
                    ierr = ERRNO_UNKNOWN_TYPE;
                    break;
            }
            break;
        default:
            ierr = ERRNO_UNKNOWN_TYPE;
            break;
    }

    return ierr;

}
